/************************************************************
 * Project Name         [Thinking_In_Java]
 * File Name            [Snake.java]
 * Creation Date        [21-Jul-2014]
 * 
 * Copyright© ge.y.yang@gmail.com All Rights Reserved
 * 
 * Work hard, play harder, think big and keep fit
 ************************************************************/
package pkg_03_game.greedysnake_V2.model;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import pkg_03_game.greedysnake_V2.constants.Constants;
import pkg_03_game.greedysnake_V2.listener.SnakeListener;

/**
 * 蛇<br/>
 * move()方法默认支持走到边以后从另一边出现<br/>
 * <br/>
 * 可以用setHeadColor(), 和 setBodyColor()方法更改蛇头或蛇身体的颜色<br/>
 * <br/>
 * 可以通过覆盖drawHead()方法改变蛇头的显示方式和覆盖, 可以通过覆盖drawBody()方法改变蛇身体的显示方式<br/>
 * <br/>
 * 用内部类MoveDriver驱动蛇定时移动<br/>
 * begin()方法内部开启一个新的线程驱动蛇定时移动, 调用这个方法的时候要注意<br/>
 * 
 * 蛇的身体的初始长度必须大于等于2
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 */
public class Snake {

	public static final int UP = 1;// 方向上
	public static final int DOWN = -1;// 方向下
	public static final int LEFT = 2; // 方向左
	public static final int RIGHT = -2;// 方向右

	/* 蛇(多个节点) */
	private final LinkedList<Point> body = new LinkedList<Point>();

	/* 上一次的移动方向 */
	private int oldDirection;

	/* 下一步的方向(有效方向) */
	private int newDirection;

	/* 临时存放蛇头的坐标 */
	private Point head;

	/* 临时存放蛇尾巴的坐标 */
	private Point tail;

	/* 移动速度 */
	private int speed;

	/* 生命, 是否活着 */
	private boolean live;

	/* 是否暂停 */
	private boolean pause;

	private final Set<SnakeListener> listeners = new HashSet<SnakeListener>();

	/* 蛇头的颜色 */
	public static final Color DEFAULT_HEAD_COLOR = new Color(0xcc0033);
	private Color headColor = DEFAULT_HEAD_COLOR;

	/* 蛇身体的颜色 */
	public static final Color DEFAULT_BODY_COLOR = new Color(0xcc0033);
	private Color bodyColor = DEFAULT_BODY_COLOR;

	/**
	 * 移动一步, 会忽略相反方向
	 */
	public void move() {
		/* 忽略相反方向 */
		if (oldDirection + newDirection != 0)
			oldDirection = newDirection;
		/* 把蛇尾巴拿出来重新设置坐标作为新蛇头 */
		/* getLocation 将返回一个新的Point */
		/* tail把尾巴坐标保存下来, 吃到食物时再加上 */
		tail = (head = takeTail()).getLocation();
		/* 根据蛇头的坐标再 上下左右 */
		head.setLocation(getHead());
		/* 根据方向让蛇移动 */
		switch (oldDirection) {
		case UP:
			head.y--;
			/* 到边上了可以从另一边出现 */
			if (head.y < 0)
				head.y = Constants.HEIGHT - 1;
			break;
		case DOWN:
			head.y++;
			/* 到边上了可以从另一边出现 */
			if (head.y == Constants.HEIGHT)
				head.y = 0;
			break;
		case LEFT:
			head.x--;
			/* 到边上了可以从另一边出现 */
			if (head.x < 0)
				head.x = Constants.WIDTH - 1;
			break;
		case RIGHT:
			head.x++;
			/* 到边上了可以从另一边出现 */
			if (head.x == Constants.WIDTH)
				head.x = 0;
			break;
		}
		/* 添加到头上去 */
		body.addFirst(head);
	}

	/**
	 * 一个内部类, 驱动蛇定时移动
	 */
	private class SnakeDriver implements Runnable {

		@Override
		public void run() {
			while (live) {
				if (!pause) {
					move();
					/* 触发 ControllerListener 的状态改变事件 */
					for (SnakeListener listener : listeners)
						listener.snakeMoved();
				}
				try {
					Thread.sleep(speed);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 在尾巴上增加一个节点
	 */
	public void eatFood() {
		/* 把上一次移动拿掉的节点再加上 */
		body.addLast(tail.getLocation());
		/* 触发SnakeListener 的 snakeEatFood 事件 */
		for (SnakeListener listener : listeners)
			listener.snakeEatFood();
	}

	/**
	 * 改变方向
	 */
	public void changeDirection(int direction) {
		newDirection = direction;
	}

	/**
	 * 得到蛇头节点
	 */
	public Point getHead() {
		/* 自己约定哪个是蛇头 */
		return body.getFirst();
	}

	/**
	 * 拿掉蛇尾巴节点
	 */
	public Point takeTail() {
		/* 去掉蛇尾巴 */
		return body.removeLast();
	}

	/**
	 * 得到蛇的长度
	 */
	public int getLength() {
		return body.size();
	}

	/**
	 * 让蛇开始运动<br/>
	 * 开启一个新的线程
	 */
	public void begin() {
		new Thread(new SnakeDriver()).start();
	}

	/**
	 * 让蛇复活, 并开始运动<br/>
	 * 将调用 begin() 方法
	 */
	public void reNew() {
		init();
		begin();
	}

	/**
	 * 初始化蛇的信息<br/>
	 * 长度, 位置, 方向, 速度, 生命和暂停状态
	 */
	public void init() {
		body.clear();
		/* 初始化位置在中间 */
		int x = Constants.WIDTH / 2 - Constants.INIT_LENGTH / 2;
		int y = Constants.HEIGHT / 2;
		for (int i = 0; i < Constants.INIT_LENGTH; i++)
			body.addFirst(new Point(x++, y));
		/* 设置默认方向为向右 */
		oldDirection = newDirection = RIGHT;
		/* 初始化速度 */
		speed = Constants.SPEED;
		/* 初始化生命和暂停状态 */
		live = true;
		pause = false;
	}

	/**
	 * 是否吃到自己的身体<br/>
	 * 
	 * @return 蛇头的坐标是否和自己的身体的某一个坐标重合
	 */
	public boolean isEatBody() {
		/* 要把蛇头排除, body.get(0) 是蛇头 */
		for (int i = 1; i < body.size(); i++)
			if (getHead().equals(body.get(i)))
				return true;
		return false;
	}

	/**
	 * 画蛇<br/>
	 * 将调用drawHead()方法和drawBody()方法
	 */
	public void drawMe(Graphics g) {
		for (Point p : body) {
			/* 画蛇身体 */
			g.setColor(bodyColor);
			drawBody(g, p.x * Constants.CELL_WIDTH,
					p.y * Constants.CELL_HEIGHT, Constants.CELL_WIDTH,
					Constants.CELL_HEIGHT);
		}
		/* 画蛇头 */
		g.setColor(headColor);
		drawHead(g, getHead().x * Constants.CELL_WIDTH, getHead().y
				* Constants.CELL_HEIGHT, Constants.CELL_WIDTH,
				Constants.CELL_HEIGHT);
	}

	/**
	 * 画蛇头, 可以覆盖这个方法改变蛇头的显示
	 */
	public void drawHead(Graphics g, int x, int y, int width, int height) {
		g.fill3DRect(x, y, width, height, true);
	}

	/**
	 * 画蛇的一节身体, 可以覆盖这个方法改变蛇的身体节点的显示
	 */
	public void drawBody(Graphics g, int x, int y, int width, int height) {
		g.fill3DRect(x, y, width, height, true);
	}

	/**
	 * 得到蛇头的颜色
	 */
	public Color getHeadColor() {
		return headColor;
	}

	/**
	 * 设置蛇头的颜色
	 */
	public void setHeadColor(Color headColor) {
		this.headColor = headColor;
	}

	/**
	 * 得到蛇身体的颜色
	 */
	public Color getBodyColor() {
		return bodyColor;
	}

	/**
	 * 设置蛇身体的颜色
	 */
	public void setBodyColor(Color bodyColor) {
		this.bodyColor = bodyColor;
	}

	/**
	 * 添加监听器
	 */
	public synchronized void addSnakeListener(SnakeListener listener) {
		if (listener == null)
			return;
		listeners.add(listener);
	}

	/**
	 * 移除监听器
	 */
	public synchronized void removeSnakeListener(SnakeListener listener) {
		if (listener == null)
			return;
		listeners.remove(listener);
	}

	/**
	 * 加速, 幅度为 Global 中设置的 SPEED_STEP <br/>
	 * 在有效的速度范围之内(增加后速度大于 0毫秒/格)
	 */
	public void speedUp() {
		if (speed > Constants.SPEED_STEP)
			speed -= Constants.SPEED_STEP;
	}

	/**
	 * 减速, 幅度为 Global 中设置的 SPEED_STEP
	 */
	public void speedDown() {
		speed += Constants.SPEED_STEP;
	}

	/**
	 * 得到蛇的移动速度
	 */
	public int getSpeed() {
		return speed;
	}

	/**
	 * 设置蛇的移动速度
	 */
	public void setSpeed(int speed) {
		this.speed = speed;
	}

	/**
	 * 蛇是否死掉了
	 */
	public boolean isLive() {
		return live;
	}

	/**
	 * 设置蛇是否死掉
	 */
	public void setLive(boolean live) {
		this.live = live;
	}

	/**
	 * 设置蛇死掉
	 */
	public void dead() {
		live = false;
	}

	/**
	 * 是否是暂停状态
	 */
	public boolean isPause() {
		return pause;
	}

	/**
	 * 设置暂停状态
	 */
	public void setPause(boolean pause) {
		this.pause = pause;
	}

	/**
	 * 更改暂停状态<br/>
	 * 若是暂停状态, 则继续移动<br/>
	 * 若正在移动, 则暂停
	 */
	public void changePause() {
		pause = !pause;
	}

}
